package com.example.demo.util;

import com.tencent.kona.crypto.KonaCryptoProvider;
import com.tencent.kona.crypto.provider.SM3MessageDigest;
import com.tencent.kona.crypto.spec.SM2PrivateKeySpec;
import com.tencent.kona.crypto.spec.SM2PublicKeySpec;
import org.springframework.web.multipart.MultipartFile;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;
import java.util.Objects;

/**
 * SM2 非对称加密算法     安全性较高,速度较慢(适合小数据量) 公钥/私钥对(256位)      数字签名,加密传输对称密钥
 * SM3 密码哈希算法      非常快                        无密钥，固定输出256位    数据完整性校验(软件包校验、区块链交易哈希、数据库记录指纹),数字指纹,密码存储
 * SM4 对称分组加密算法   快(适合大数据量)                单密钥(128/192/256位) 数据传输加密,数据库字段加密,视频加密
 *
 * @author haochen78
 * @date 2025/7/7 09:13
 **/
public class CryptoUtil {

    //注意：一般js库或者BC库，都没有按照ASN.1 DER 格式来处理。但是国密标准中密文传输推荐采用ASN.1 DER 格式，所以Kona默认生成和接受的都是该格式的数据，和这些工具对接时要注意转换，Kona提供了一个转换工具 SM2Ciphertext
    // C1||C3||C2 with ASN.1 DER 格式转成原始的C1C3C2格式
    //byte[] rawC1C3C2 = SM2Ciphertext.builder()
    //        // 指定输入数据的格式
    //        .format(SM2Ciphertext.Format.DER_C1C3C2)
    //        // 按输入格式进行编码
    //        .encodedCiphertext(temp)
    //        .build()
    //        // 转换格式
    //        .rawC1C3C2();

    // 将原始的C1C3C2格式转换成C1||C3||C2 with ASN.1 DER 格式
// js加密的数据一般都不带04前缀，没有的情况补上
//if (!ciphertext.startsWith("04")){
//        ciphertext = "04" + ciphertext;
//    }
//    byte[] derC1C3C2 = SM2Ciphertext.builder()
//            .format(SM2Ciphertext.Format.RAW_C1C3C2)
//            .encodedCiphertext(ciphertext)
//            .build()
//            .derC1C3C2();

    // 在使用KonaCrypto中的任何特性之前，必须要加载KonaCryptoProvider
    static {
        Security.addProvider(new KonaCryptoProvider());
    }

    // 在类顶部添加常量定义
    private static final String SM_ALGORITHM_TWO = "SM2";
    private static final String SM_ALGORITHM_THREE = "SM3";
    private static final String SM_ALGORITHM_FOUR = "SM4";
    private static final String SM4_CBC_PKCS7PADDING = "SM4/CBC/PKCS7Padding";
    private static final String DEFAULT_CHARSET = StandardCharsets.UTF_8.name();

    // 在类中添加自定义异常类
    public static class CryptoException extends RuntimeException {
        public CryptoException(String message) {
            super(message);
        }

        public CryptoException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    // 添加Base64编解码工具方法
    private static byte[] decodeBase64(String data) {
        return Base64.getDecoder().decode(data);
    }

    private static String encodeBase64(byte[] data) {
        return Base64.getEncoder().encodeToString(data);
    }

    public static void main(String[] args) throws Exception {
        //// 初始化 SM2 密钥对生成器
        //KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(SM_ALGORITHM_TWO);
        //// 拿到密钥对象
        //KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //PublicKey publicKey = keyPair.getPublic();
        //PrivateKey privateKey = keyPair.getPrivate();
        //// 将公钥和私钥转换成字符串，方便传输存储
        //String publicKeyStr = encodeBase64(publicKey.getEncoded());
        //String privateKeyStr = encodeBase64(privateKey.getEncoded());
        //
        //System.out.println("公钥：" + publicKeyStr);
        //System.out.println("私钥：" + privateKeyStr);

        String publicKeyStr = "BPat/+NWB7rtN7LGP+9gLZ9nLWxQzA0exwMM8ulku+wo6WYjMV3okW7KRioqEB7GghRFT2PiArWc8SQUWVBhVHI=";
        String privateKeyStr = "2demksNL3uTwRH0csyuP+keilX/MPoPSQY4h/GuMzHg=";

        String str = "今天气真好啊";
        String s2 = sm3CalculateHash(str);
        System.out.println(s2);
        String s = sm2Encrypt(publicKeyStr, s2);
        System.out.println(s);
        System.out.println("================================");

        String s1 = sm2Decrypt(privateKeyStr, s);
        System.out.println(s1);
        boolean verify = sm3VerifyHash(str, s1);
        System.out.println(verify);

        //// 生成随机SM4密钥
        //String key128 = generateKey(128);
        ////String key192 = generateKey(192);
        ////String key256 = generateKey(256);
        //System.out.println("128-bit Key: " + key128);
        ////System.out.println("192-bit Key: " + key192);
        ////System.out.println("256-bit Key: " + key256);
        //
        //// 生成随机SM4 iv
        //String iv16 = generateIv(16);
        //String iv12 = generateIv(12);
        //System.out.println("16-byte iv: " + iv16);
        //System.out.println("12-byte iv: " + iv12);
    }

    /**
     * sm2公钥加密
     * <p>出于性能考虑，与其它的非对称加密算法（如RSA和EC）相同，SM2加密算法一般只用于加密少量的关键性数据.</p>
     * <p>生成的格式为标准的C1||C3||C2 with ASN.1 DER 格式，如需其他格式请自行转换</p>
     *
     * @param publicKeyBase64 公钥
     * @param data            待加密数据
     * @return 加密后的数据(16进制字符串 ， C1 | | C3 | | C2 with ASN.1 DER 格式)
     */
    public static String sm2Encrypt(String publicKeyBase64, String data) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(SM_ALGORITHM_TWO);
            SM2PublicKeySpec publicKeySpec = new SM2PublicKeySpec(decodeBase64(publicKeyBase64));
            PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
            Cipher cipher = Cipher.getInstance(SM_ALGORITHM_TWO);
            // 使用公钥对Cipher进行初始化，指定其使用加密模式。
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] message = data.getBytes(DEFAULT_CHARSET);
            byte[] cipherText = cipher.doFinal(message);
            return encodeBase64(cipherText);
        } catch (Exception e) {
            throw new CryptoException("SM2 encryption failed: " + e.getMessage(), e);
        }
    }

    /**
     * sm2私钥解密，密文格式为C1||C3||C2 with ASN.1 DER 格式，如果是其他格式请先使用SM2Ciphertext工具类进行转换
     *
     * @param privateKeyBase64 sm2私钥(16进制字符串）
     * @param data             待解密数据(16进制字符串,C1||C3||C2 with ASN.1 DER 格式）
     */
    public static String sm2Decrypt(String privateKeyBase64, String data) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance(SM_ALGORITHM_TWO);
            SM2PrivateKeySpec privateKeySpec = new SM2PrivateKeySpec(decodeBase64(privateKeyBase64));
            PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
            // 初始化cipher
            Cipher cipher = Cipher.getInstance(SM_ALGORITHM_TWO);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] dataByte = decodeBase64(data);
            byte[] clearText = cipher.doFinal(dataByte);
            return new String(clearText, DEFAULT_CHARSET);
        } catch (Exception e) {
            throw new CryptoException("SM2 decryption failed: " + e.getMessage(), e);
        }
    }

    /**
     * 创建摘要
     *
     * @param content 需要生成摘要的消息内容
     * @return 摘要
     */
    public static String sm3CalculateHash(String content) {
        try {
            SM3MessageDigest sm3 = new SM3MessageDigest();
            byte[] dataBytes = content.getBytes(StandardCharsets.UTF_8);
            sm3.update(dataBytes, 0, dataBytes.length);
            byte[] hashBytes = sm3.digest();
            return Base64.getEncoder().encodeToString(hashBytes);
        } catch (Exception e) {
            throw new CryptoException("SM3 digest failed: " + e.getMessage(), e);
        }
    }

    /**
     * 验证数据的SM3哈希值是否匹配
     *
     * @param data         原始数据
     * @param expectedHash 预期的SM3哈希值（Base64编码）
     * @return 验证通过返回true，否则返回false
     */
    public static boolean sm3VerifyHash(String data, String expectedHash) {
        // 入参为空直接验证失败
        if (data == null || expectedHash == null) {
            return false;
        }
        // 计算原始数据的SM3哈希
        String actualHash = sm3CalculateHash(data);
        // 比较计算结果与预期哈希（使用常量时间比较，防止时序攻击）
        return constantTimeEquals(actualHash, expectedHash);
    }

    /**
     * 常量时间比较（防止时序攻击，适用于哈希、密码等敏感信息比较）
     * equals()：如果第 1 位就不同，耗时约 1ms；如果最后 1 位不同，耗时约 32ms → 差异明显。
     * constantTimeEquals：无论差异在第 1 位还是第 32 位，耗时均为 32ms → 无差异。
     */
    private static boolean constantTimeEquals(String a, String b) {
        if (a.length() != b.length()) {
            return false;
        }
        int result = 0;
        for (int i = 0; i < a.length(); i++) {
            result |= a.charAt(i) ^ b.charAt(i);
        }
        return result == 0;
    }

    /**
     * 计算文件的 SM3 哈希值
     */
    public static String sm3CalculateHash(File file) {
        Objects.requireNonNull(file, "文件不能为空");
        try (InputStream inputStream = Files.newInputStream(file.toPath())) {
            return sm3CalculateHash(inputStream);
        } catch (IOException e) {
            throw new CryptoException("SM3 calculate file hash failed: " + e.getMessage(), e);
        }
    }

    /**
     * 计算文件的SM3哈希值
     */
    public static String sm3CalculateHash(MultipartFile file) {
        Objects.requireNonNull(file, "文件不能为空");
        try (InputStream inputStream = file.getInputStream()) {
            return sm3CalculateHash(inputStream);
        } catch (IOException e) {
            throw new CryptoException("SM3 calculate multipart file hash failed: " + e.getMessage(), e);
        }
    }

    /**
     * 计算文件哈希值
     */
    public static String sm3CalculateHash(InputStream inputStream) {
        try {
            MessageDigest sm3Digest = MessageDigest.getInstance(SM_ALGORITHM_THREE);
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                sm3Digest.update(buffer, 0, bytesRead);
            }
            byte[] hashBytes = sm3Digest.digest();
            return encodeBase64(hashBytes);
        } catch (Exception e) {
            throw new CryptoException("SM3 calculate input stream hash failed: " + e.getMessage(), e);
        }
    }

    /**
     * 计算字节数组的 SM3 哈希值
     */
    public static String sm3CalculateHash(byte[] data) {
        try {
            MessageDigest sm3Digest = MessageDigest.getInstance(SM_ALGORITHM_THREE);
            byte[] hashBytes = sm3Digest.digest(data);
            return encodeBase64(hashBytes);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SM3 算法不可用", e);
        }
    }

    /**
     * 生成指定长度的SM4密钥(128/192/256位)
     */
    private static String generateKey(int keySize) {
        if (keySize != 128 && keySize != 192 && keySize != 256) {
            throw new CryptoException("无效的密钥长度: " + keySize);
        }
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(SM_ALGORITHM_FOUR);
            keyGenerator.init(keySize, new SecureRandom());
            SecretKey secretKey = keyGenerator.generateKey();
            byte[] keyBytes = secretKey.getEncoded();
            return encodeBase64(keyBytes);
        } catch (NoSuchAlgorithmException e) {
            throw new CryptoException("generate key failed: " + e.getMessage(), e);
        }
    }

    /**
     * 生成指定长度的SM4 IV/nonce(ECB:无需IV,CBC:16字节,CTR:12字节,GCM:12字节)
     */
    private static String generateIv(int keySize) {
        if (keySize != 16 && keySize != 12) {
            throw new IllegalArgumentException("无效的IV/nonce长度: " + keySize);
        }
        // 创建 SecureRandom 实例以生成加密安全的随机数
        SecureRandom random = new SecureRandom();
        // 初始化一个16字节的字节数组来存储 IV
        byte[] iv = new byte[keySize];
        // 用安全随机数填充 IV
        random.nextBytes(iv);
        return encodeBase64(iv);
    }

    /**
     * SM4/ECB/PKCS7Padding对称加密算法
     * 加密随机数据（如密钥token加密等）
     * 无初始化向量(IV)，相同明文块产生相同密文块，并行计算友好
     *
     * @param keyBase64 单密钥(128/192/256位)
     * @param content   消息内容
     */
    public static String encryptByEcb(String keyBase64, String content) {
        try {
            byte[] key = decodeBase64(keyBase64);
            SecretKey secretKey = new SecretKeySpec(key, SM_ALGORITHM_FOUR);
            Cipher cipher = Cipher.getInstance(SM4_CBC_PKCS7PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new SecureRandom());
            byte[] contentByte = content.getBytes(DEFAULT_CHARSET);
            byte[] ciphertext = cipher.doFinal(contentByte);
            return encodeBase64(ciphertext);
        } catch (Exception e) {
            throw new CryptoException("SM4 ECB encryption failed: " + e.getMessage(), e);
        }
    }

    /**
     * SM4/ECB/PKCS7Padding对称解密
     *
     * @param keyBase64     单密钥(128/192/256位)
     * @param cipherContent 密文
     */
    public static String decryptByEcb(String keyBase64, String cipherContent) {
        try {
            byte[] key = decodeBase64(keyBase64);
            SecretKey secretKey = new SecretKeySpec(key, SM_ALGORITHM_FOUR);
            Cipher cipher = Cipher.getInstance(SM4_CBC_PKCS7PADDING);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new SecureRandom());
            byte[] cipherByte = decodeBase64(cipherContent);
            byte[] clearText = cipher.doFinal(cipherByte);
            return new String(clearText, DEFAULT_CHARSET);
        } catch (Exception e) {
            throw new CryptoException("SM4 ECB decryption failed: " + e.getMessage(), e);
        }
    }


    /**
     * SM4/CBC/PKCS7Padding对称加密算法
     * 文件加密,数据库字段加密等
     * 需要16字节IV,错误传播（一个块损坏影响后续块）,隐藏明文模式
     *
     * @param keyBase64 单密钥(128位)
     * @param content   消息内容
     * @param ivBase64  初始化向量(16字节)
     */
    public static String encryptByCbc(String keyBase64, String content, String ivBase64) {
        try {
            byte[] key = decodeBase64(keyBase64);
            SecretKey secretKey = new SecretKeySpec(key, SM_ALGORITHM_FOUR);
            Cipher cipher = Cipher.getInstance(SM4_CBC_PKCS7PADDING);
            byte[] iv = decodeBase64(ivBase64);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv), new SecureRandom());
            byte[] contentByte = content.getBytes(DEFAULT_CHARSET);
            byte[] ciphertext = cipher.doFinal(contentByte);
            return encodeBase64(ciphertext);
        } catch (Exception e) {
            throw new CryptoException("SM4 CBC encryption failed: " + e.getMessage(), e);
        }
    }

    /**
     * SM4/CBC/PKCS7Padding对称解密
     *
     * @param keyBase64     单密钥(128位)
     * @param cipherContent 密文
     * @param ivBase64      初始化向量(16字节)
     */
    public static String decryptByCbc(String keyBase64, String cipherContent, String ivBase64) {
        try {
            Cipher cipher = Cipher.getInstance(SM4_CBC_PKCS7PADDING);
            byte[] key = decodeBase64(keyBase64);
            SecretKey secretKey = new SecretKeySpec(key, SM_ALGORITHM_FOUR);
            byte[] iv = decodeBase64(ivBase64);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv), new SecureRandom());
            byte[] cipherByte = decodeBase64(cipherContent);
            byte[] cleartext = cipher.doFinal(cipherByte);
            return new String(cleartext, DEFAULT_CHARSET);
        } catch (Exception e) {
            throw new CryptoException("SM4 CBC decryption failed: " + e.getMessage(), e);
        }
    }

    public void encryptFileByCbc(String keyBase64, String ivBase64, File inputFile, File outputFile) {
        try (InputStream in = Files.newInputStream(inputFile.toPath()); OutputStream out = Files.newOutputStream(outputFile.toPath())) {
            encryptFileByCbc(keyBase64, ivBase64, in, out);
        } catch (IOException e) {
            throw new CryptoException("SM4 CBC file encryption failed: " + e.getMessage(), e);
        }
    }

    public static void encryptFileByCbc(String keyBase64, String ivBase64, InputStream inputStream, OutputStream outputStream) {
        try {
            byte[] key = decodeBase64(keyBase64);
            SecretKey secretKey = new SecretKeySpec(key, SM_ALGORITHM_FOUR);
            byte[] iv = decodeBase64(ivBase64);
            // 创建Cipher实例
            Cipher cipher = Cipher.getInstance(SM4_CBC_PKCS7PADDING);
            // 初始化cipher，指定使用加密模式
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
            try (CipherOutputStream cos = new CipherOutputStream(outputStream, cipher)) {
                // 使用流模式，避免将整个文件加载到内存 适当调整缓冲区大小（如 8KB~64KB）
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    cos.write(buffer, 0, bytesRead);
                }
            }
        } catch (Exception e) {
            throw new CryptoException("SM4 CBC stream encryption failed: " + e.getMessage(), e);
        }
    }

    public static void decryptFileByCbc(String keyBase64, String ivBase64, File inputFile, File outputFile) {
        try (InputStream in = Files.newInputStream(inputFile.toPath()); OutputStream out = Files.newOutputStream(outputFile.toPath())) {
            decryptFileByCbc(keyBase64, ivBase64, in, out);
        } catch (Exception e) {
            throw new CryptoException("SM4 CBC file decryption failed: " + e.getMessage(), e);
        }
    }

    public static void decryptFileByCbc(String keyBase64, String ivBase64, InputStream inputStream, OutputStream outputStream) {
        try {
            byte[] key = decodeBase64(keyBase64);
            SecretKey secretKey = new SecretKeySpec(key, SM_ALGORITHM_FOUR);
            byte[] iv = decodeBase64(ivBase64);
            // 创建Cipher实例
            Cipher cipher = Cipher.getInstance(SM4_CBC_PKCS7PADDING);
            // 初始化cipher，指定使用解密模式
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
            // 解密
            try (CipherInputStream cis = new CipherInputStream(inputStream, cipher)) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = cis.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
            }
        } catch (Exception e) {
            throw new CryptoException("SM4 CBC stream decryption failed: " + e.getMessage(), e);
        }
    }
}
